import * as vscode from 'vscode';
import { SerialPort } from 'serialport';
import WebSocket from 'ws';

export enum ConnectionType {
    SERIAL = 'serial',
    WEBREPL = 'webrepl'
}

export enum ConnectionState {
    DISCONNECTED = 'disconnected',
    CONNECTING = 'connecting',
    CONNECTED = 'connected',
    ERROR = 'error'
}

export interface DeviceInfo {
    name: string;
    type: ConnectionType;
    port?: string;
    url?: string;
    connected: boolean;
}

export class DeviceConnectionManager {
    private serialPort?: SerialPort;
    private webSocket?: WebSocket;
    private connectionType?: ConnectionType;
    private connectionState: ConnectionState = ConnectionState.DISCONNECTED;
    private onStateChangeEmitter = new vscode.EventEmitter<ConnectionState>();
    private onDataReceivedEmitter = new vscode.EventEmitter<string>();
    private onRawDataReceivedEmitter = new vscode.EventEmitter<Buffer>();

    public readonly onStateChange = this.onStateChangeEmitter.event;
    public readonly onDataReceived = this.onDataReceivedEmitter.event;
    public readonly onRawDataReceived = this.onRawDataReceivedEmitter.event;

    public get state(): ConnectionState {
        return this.connectionState;
    }

    public get isConnected(): boolean {
        return this.connectionState === ConnectionState.CONNECTED;
    }

    public async connectSerial(port: string, baudRate: number = 115200): Promise<boolean> {
        try {
            this.setState(ConnectionState.CONNECTING);

            this.serialPort = new SerialPort({
                path: port,
                baudRate: baudRate,
                autoOpen: false
            });

            return new Promise((resolve, reject) => {
                this.serialPort!.open((err) => {
                    if (err) {
                        this.setState(ConnectionState.ERROR);

                        // Provide user-friendly error messages
                        let errorMessage = `Failed to open serial port ${port}: `;
                        if (err.message.includes('Access is denied') || err.message.includes('error code 31')) {
                            errorMessage += 'Port is already in use by another application. Please close any other serial terminals (Arduino IDE, PuTTY, Thonny, etc.) and try again.';
                        } else if (err.message.includes('File not found') || err.message.includes('cannot open')) {
                            errorMessage += 'Port not found. Please check if the device is connected and drivers are installed.';
                        } else {
                            errorMessage += err.message;
                        }

                        const enhancedError = new Error(errorMessage);
                        enhancedError.stack = err.stack;
                        reject(enhancedError);
                        return;
                    }

                    this.connectionType = ConnectionType.SERIAL;
                    this.setState(ConnectionState.CONNECTED);
                    this.setupSerialListeners();
                    resolve(true);
                });
            });
        } catch (error) {
            this.setState(ConnectionState.ERROR);
            throw error;
        }
    }

    public async connectWebREPL(url: string, password?: string): Promise<boolean> {
        try {
            this.setState(ConnectionState.CONNECTING);

            this.webSocket = new WebSocket(url);

            return new Promise((resolve, reject) => {
                this.webSocket!.on('open', () => {
                    this.connectionType = ConnectionType.WEBREPL;
                    this.setState(ConnectionState.CONNECTED);
                    this.setupWebSocketListeners();

                    // Send password if provided
                    if (password) {
                        this.sendCommand(password);
                    }

                    resolve(true);
                });

                this.webSocket!.on('error', (error) => {
                    this.setState(ConnectionState.ERROR);
                    reject(error);
                });
            });
        } catch (error) {
            this.setState(ConnectionState.ERROR);
            throw error;
        }
    }

    public async disconnect(): Promise<void> {
        if (this.serialPort?.isOpen) {
            await new Promise<void>((resolve) => {
                this.serialPort!.close(() => resolve());
            });
        }

        if (this.webSocket && this.webSocket.readyState === WebSocket.OPEN) {
            this.webSocket.close();
        }

        this.serialPort = undefined;
        this.webSocket = undefined;
        this.connectionType = undefined;
        this.setState(ConnectionState.DISCONNECTED);
    }

    public async sendCommand(command: string): Promise<void> {
        if (!this.isConnected) {
            throw new Error('Device not connected');
        }

        const data = command + '\r\n';

        if (this.connectionType === ConnectionType.SERIAL && this.serialPort) {
            return new Promise((resolve, reject) => {
                this.serialPort!.write(data, (error) => {
                    if (error) {
                        console.error('Failed to write to serial port:', error);
                        reject(error);
                    } else {
                        // 确保数据被发送到硬件
                        this.serialPort!.drain((drainError) => {
                            if (drainError) {
                                console.error('Failed to drain serial port:', drainError);
                                reject(drainError);
                            } else {
                                resolve();
                            }
                        });
                    }
                });
            });
        } else if (this.connectionType === ConnectionType.WEBREPL && this.webSocket) {
            this.webSocket.send(data);
            return Promise.resolve();
        }
    }

    public async sendRawData(data: Buffer | string): Promise<void> {
        if (!this.isConnected) {
            throw new Error('Device not connected');
        }

        const buffer = typeof data === 'string' ? Buffer.from(data) : data;

        if (this.connectionType === ConnectionType.SERIAL && this.serialPort) {
            return new Promise((resolve, reject) => {
                this.serialPort!.write(buffer, (error) => {
                    if (error) {
                        console.error('Failed to write to serial port:', error);
                        reject(error);
                    } else {
                        // 确保数据被发送到硬件
                        this.serialPort!.drain((drainError) => {
                            if (drainError) {
                                console.error('Failed to drain serial port:', drainError);
                                reject(drainError);
                            } else {
                                resolve();
                            }
                        });
                    }
                });
            });
        } else if (this.connectionType === ConnectionType.WEBREPL && this.webSocket) {
            this.webSocket.send(buffer);
            return Promise.resolve();
        }
    }

    private setupSerialListeners(): void {
        if (!this.serialPort) return;

        this.serialPort.on('data', (data: Buffer) => {
            // 发射原始二进制数据（用于 Raw REPL 协议）
            this.onRawDataReceivedEmitter.fire(data);
            // 发射字符串数据（用于 REPL 终端显示）
            this.onDataReceivedEmitter.fire(data.toString());
        });

        this.serialPort.on('error', (error) => {
            console.error('Serial port error:', error);
            this.setState(ConnectionState.ERROR);
        });

        this.serialPort.on('close', () => {
            this.setState(ConnectionState.DISCONNECTED);
        });
    }

    private setupWebSocketListeners(): void {
        if (!this.webSocket) return;

        this.webSocket.on('message', (data: WebSocket.Data) => {
            // 转换为 Buffer
            const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data.toString());
            // 发射原始二进制数据（用于 Raw REPL 协议）
            this.onRawDataReceivedEmitter.fire(buffer);
            // 发射字符串数据（用于 REPL 终端显示）
            this.onDataReceivedEmitter.fire(data.toString());
        });

        this.webSocket.on('error', (error) => {
            console.error('WebSocket error:', error);
            this.setState(ConnectionState.ERROR);
        });

        this.webSocket.on('close', () => {
            this.setState(ConnectionState.DISCONNECTED);
        });
    }

    private setState(state: ConnectionState): void {
        this.connectionState = state;
        this.onStateChangeEmitter.fire(state);
    }

    public dispose(): void {
        this.disconnect();
        this.onStateChangeEmitter.dispose();
        this.onDataReceivedEmitter.dispose();
        this.onRawDataReceivedEmitter.dispose();
    }
}
